﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using OpenTap.Plugins.Interfaces.Clock;

namespace OpenTap.Plugins.Clock
{
    [Display("Keysight Pxi M9300a Clock", Group: "OpenTap.Plugins", Description: "Keysight Pxi M9300a Clock Driver")]
    public class KeysightM9300aClock : Instrument, IClock
    {
        private  Type _loadedAssemblyType;
        private  object _instanceAssembly;

        #region Settings

        [Display("PXI Address", Order: 1, Description: "Pxi physics connecting address")]
        public string PxiAddress { get; set; }

        [Display("Enable Simulate", Order: 2, Description: "use the Simulate(true) mode to create connection test")]
        public bool IsSimulate { get; set; }

        #endregion

        public KeysightM9300aClock()
        {
            IdnString = "Pxi Clock M9300a"; //TODO: fix this to come from the instrument!
            IsSimulate = false;
            PxiAddress = "PXI21::0::0::INSTR";
            Name = "Pxi Clock M9300a";
        }

        /// <inheritdoc />
        public override void Open()
        {
            base.Open();
            try
            {
                _loadedAssemblyType = Type.GetTypeFromProgID("AgM9300.AgM9300", true);
                _instanceAssembly = Activator.CreateInstance(_loadedAssemblyType);

                var options = $"QueryInstrStatus=true, Simulate={IsSimulate}, DriverSetup=, Model=, Trace=false";
                var parameter = new object[] { PxiAddress, true, true, options };

                var method = _loadedAssemblyType.GetMethod("Initialize");
                if(method != null)
                    method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, parameter, CultureInfo.InvariantCulture);

                var propertyInfo = _loadedAssemblyType.GetProperty("Description", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (propertyInfo == null) return;
                var description = propertyInfo.GetValue(_instanceAssembly);

                Log.Info("PXI Reference Clock Open Success , Description:" + description);
            }
            catch(Exception ex)
            {
                Log.Error("the PXI Reference Clock Initialize failed:\n" + ex);
            }
        }

        /// <inheritdoc />
        public override void Close()
        {
            var propertyInfo = _loadedAssemblyType.GetProperty("Initialized", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (propertyInfo != null)
            {
                var initialed = propertyInfo.GetValue(_instanceAssembly);
                if (initialed.Equals(true))
                {
                    var method = _loadedAssemblyType.GetMethod("Close");
                    if(method != null)
                        method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, null, CultureInfo.InvariantCulture);
                }
            }
            Log.Info("PXI Reference Clock: closed!");
            base.Close();
        }

        public void WaitForOperationComplete(int timeoutMs = 2000)
        {
            throw new NotImplementedException();
        }

        public void Reset()
        {
            var method = _loadedAssemblyType.GetMethod("Reset");
            if(method != null)
                method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, null, CultureInfo.InvariantCulture);
        }

        public List<ScpiInstrument.ScpiError> QueryErrors(bool suppressLogMessages = false, int maxErrors = 1000)
        {
            throw new NotImplementedException();
        }

        public string IdnString { get; }
        public void Clear()
        {
            throw new NotImplementedException();
        }

        public int SelfTest(out string testMessage)
        {
            throw new NotImplementedException();
        }

        public void SetReferenceOutputStatus(EOutputPort outputPort, bool enable)
        {
            switch (outputPort)
            {
                case EOutputPort.Out100MHzPort1:
                    var propertyInfo = _loadedAssemblyType.GetProperty("Out1Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if(propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out100MHzPort2:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out2Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out100MHzPort3:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out3Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out100MHzPort4:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out4Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out100MHzPort5:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out5Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out100MhzBpPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("BPOutEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.OutOcxoPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("OCXOEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                case EOutputPort.Out10MhzPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("Ref10MHzOutEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        propertyInfo.SetValue(_instanceAssembly, enable);
                    break;

                default:
                    throw new ApplicationException("The reference clock don't support this output port:" + nameof(outputPort));
            }
            var method = _loadedAssemblyType.GetMethod("Apply");
            if(method != null)
                method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, null, CultureInfo.InvariantCulture);
        }

        public bool GetReferenceOutputStatus(EOutputPort outputPort)
        {
            object retBoolean = null;
            switch (outputPort)
            {
                case EOutputPort.Out100MHzPort1:
                    var propertyInfo = _loadedAssemblyType.GetProperty("Out1Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if(propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out100MHzPort2:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out2Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out100MHzPort3:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out3Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out100MHzPort4:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out4Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out100MHzPort5:
                    propertyInfo = _loadedAssemblyType.GetProperty("Out5Enabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out100MhzBpPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("BPOutEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.OutOcxoPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("OCXOEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                case EOutputPort.Out10MhzPort:
                    propertyInfo = _loadedAssemblyType.GetProperty("Ref10MHzOutEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if (propertyInfo != null)
                        retBoolean = propertyInfo.GetValue(_instanceAssembly);
                    break;

                default:
                    throw new ApplicationException("The reference clock don't support this output port:" + nameof(outputPort));
            }
            return Convert.ToBoolean(retBoolean);
        }

        public void SetExternalReference(double refFreqMhz, bool useExtRef)
        {
            if (refFreqMhz > 110 || refFreqMhz < 1)
                throw new ApplicationException("The external reference Freq only support 1Mhz to 110Mhz");

            var propertyInfo = _loadedAssemblyType.GetProperty("ExternalReferenceFrequency", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if(propertyInfo != null)
                propertyInfo.SetValue(_instanceAssembly, refFreqMhz * 100000);

            propertyInfo = _loadedAssemblyType.GetProperty("ExternalReferenceEnabled", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if (propertyInfo != null)
                propertyInfo.SetValue(_instanceAssembly, useExtRef);

            var method = _loadedAssemblyType.GetMethod("Apply");
            if (method != null)
                method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, null, CultureInfo.InvariantCulture);
        }

        public string Identify()
        {
            object manufacturer = null;
            object model = null;
            object serialNumber = null;
            object firmwareRev = null;

            var propertyInfo = _loadedAssemblyType.GetProperty("InstrumentManufacturer", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            if(propertyInfo != null)
            {
                manufacturer = propertyInfo.GetValue(_instanceAssembly);

                propertyInfo = _loadedAssemblyType.GetProperty("InstrumentModel", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if(propertyInfo != null)
                {
                    model = propertyInfo.GetValue(_instanceAssembly);

                    propertyInfo = _loadedAssemblyType.GetProperty("SerialNumber", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    if(propertyInfo != null)
                    {
                        serialNumber = propertyInfo.GetValue(_instanceAssembly);

                        propertyInfo = _loadedAssemblyType.GetProperty("InstrumentFirmwareRevision", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                        if(propertyInfo != null)
                        {
                            firmwareRev = propertyInfo.GetValue(_instanceAssembly);
                        }
                    }
                }
            }

            return "Manufacturer:" + manufacturer +
                   ", Model:" + model +
                   ", SerialNumber:" + serialNumber +
                   ", FirmwareRev:" + firmwareRev;
        }

        public bool SystemError(out string error)
        {
            var parameter = new object[] { -999, "ERRor: Query error message." };

            var method = _loadedAssemblyType.GetMethod("ErrorQuery");
            if(method != null)
                method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, parameter, CultureInfo.InvariantCulture);

            error = parameter.GetValue(1).ToString();
            return Convert.ToBoolean(parameter.GetValue(0));
        }

        public int Selftest(out string testMessage)
        {
            var parameter = new object[] { -999, "ERRor: SelfTest not started." };

            var method = _loadedAssemblyType.GetMethod("SelfTest");
            if(method != null)
                method.Invoke(_instanceAssembly, BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, Type.DefaultBinder, parameter, CultureInfo.InvariantCulture) ;

            testMessage = parameter.GetValue(1).ToString();
            return Convert.ToInt32(parameter.GetValue(0));
        }
    }
}
